﻿using System.Collections;
using System.ComponentModel;
using System.Dynamic;
using System;
using System.Reflection;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Collections.Generic;
using System.Windows;

namespace Mesuro.GUI {
	public class CommonPropertyProxy : DynamicObject, INotifyPropertyChanged {
		public const string PROP_Collection = "Collection";
		public const string PROP_CommonBaseType = "CommonBaseType";

		private List<String> _knownProperties = new List<string>();

		private IEnumerable _collection;
		public IEnumerable Collection {
			get { return _collection; }
			set {
				if (_collection is INotifyCollectionChanged)
					((INotifyCollectionChanged)_collection).CollectionChanged -= OnCollectionChanged;

				_collection = value;

				if (_collection is INotifyCollectionChanged)
					((INotifyCollectionChanged)_collection).CollectionChanged += OnCollectionChanged;

				if (PropertyChanged != null)
					PropertyChanged( this, new PropertyChangedEventArgs( PROP_Collection ) );

				CommonBaseType = GetCommonBaseClass( _collection );
			}
		}

		private Type _commonBaseType;
		public Type CommonBaseType {
			get {
				return _commonBaseType ?? GetCommonBaseClass( _collection );
			}
			private set {
				if (value == null && _commonBaseType == null) return;
				if (value != null && value.Equals( _commonBaseType )) return;
				_commonBaseType = value;

				if (PropertyChanged != null)
					PropertyChanged( this, new PropertyChangedEventArgs( PROP_CommonBaseType ) );
			}
		}

		public CommonPropertyProxy() { }
		public CommonPropertyProxy( IEnumerable collection ) { Collection = collection; }

		public static Type GetCommonBaseClass( params Object[] objects ) {
			return GetCommonBaseClass( (IEnumerable)objects );
		}
		public static Type GetCommonBaseClass( IEnumerable objects ) {
			if (objects == null)
				throw new ArgumentNullException( "objects" );

			IEnumerator it = objects.GetEnumerator();

			if (!it.MoveNext())
				return null;

			Type ret = (it.Current as Type) ?? ((it.Current == null) ? null : it.Current.GetType());

			while (it.MoveNext()) {
				Type tElem = (it.Current as Type) ?? ((it.Current == null) ? null : it.Current.GetType());

				if (ret == null) { ret = tElem; continue; }

				if (tElem == null || ret.IsAssignableFrom( tElem )) continue;

				while (!tElem.IsAssignableFrom( ret ))
					tElem = tElem.BaseType;

				ret = tElem;
			}
			return ret;
		}

		public override bool TryGetMember( GetMemberBinder binder, out object result ) {
			Debug.WriteLine( "Proxy Getting " + binder.Name + "..." );

			if (!_knownProperties.Contains( binder.Name ))
				_knownProperties.Add( binder.Name );

			IEnumerator it = _collection.GetEnumerator();

			if (!it.MoveNext()) {
				result = null;
				//Debug.WriteLine( "No elements in collection" );
				return true;
			}

			Type t = it.Current.GetType();
			PropertyInfo pinf = t.GetProperty( binder.Name );
			if (pinf == null) {
				result = null;
				//Debug.WriteLine( "Property doesn'selectionType exist." );
				return true;
			}
			result = pinf.GetValue( it.Current, null );

			if (result == null) {
				//Debug.WriteLine( "Null result" );
				return true;
			}
			while (it.MoveNext())
				if (!result.Equals( it.Current.GetType().GetProperty( binder.Name ).GetValue( it.Current, null ) )) {
					result = null;
					//Debug.WriteLine( "Null result" );
					return true;
				}

			//Debug.WriteLine( "Result: " + result.ToString() );
			return true;
		}
		public override bool TrySetMember( SetMemberBinder binder, object value ) {
			Debug.WriteLine( "Proxy Setting " + binder.Name + "..." );
			IEnumerator it = _collection.GetEnumerator();

			while (it.MoveNext())
				it.Current.GetType().GetProperty( binder.Name ).SetValue( it.Current, value, null );

			return true;
		}

		public override bool TryInvokeMember( InvokeMemberBinder binder, object[] args, out object result ) {
			Debug.WriteLine( "Proxy Invoking " + binder.Name + "..." );
			return base.TryInvokeMember( binder, args, out result );
		}

		public override bool TryGetIndex( GetIndexBinder binder, object[] indexes, out object result ) {
			return base.TryGetIndex( binder, indexes, out result );
		}
		public override bool TrySetIndex( SetIndexBinder binder, object[] indexes, object value ) {
			return base.TrySetIndex( binder, indexes, value );
		}

		public override bool TryConvert( ConvertBinder binder, out object result ) {
			Debug.WriteLine( "Proxy Converting to " + binder.Type.Name + "..." );
			return base.TryConvert( binder, out result );
		}
		public override bool TryBinaryOperation( BinaryOperationBinder binder, object arg, out object result ) {
			Debug.WriteLine( "Proxy Binary:" + binder.Operation + "..." );
			return base.TryBinaryOperation( binder, arg, out result );
		}
		public override bool TryUnaryOperation( UnaryOperationBinder binder, out object result ) {
			Debug.WriteLine( "Proxy Unary:" + binder.Operation + "..." );
			return base.TryUnaryOperation( binder, out result );
		}
		private void OnCollectionChanged( object sender, NotifyCollectionChangedEventArgs e ) {
			if (e.Action.HasFlag( NotifyCollectionChangedAction.Remove ) || e.Action.HasFlag( NotifyCollectionChangedAction.Reset )) {
				//Something's been removed, recalculate from scratch
				CommonBaseType = GetCommonBaseClass( _collection );
				return;
			}
			if (e.Action.HasFlag( NotifyCollectionChangedAction.Add )) {
				if (e.NewItems != null)
					CommonBaseType = GetCommonBaseClass( CommonBaseType, GetCommonBaseClass( e.NewItems ) );
				else
					CommonBaseType = GetCommonBaseClass( _collection );
			}

			//Notify all listeners that all properties may have chnaged
			List<string> old = _knownProperties;
			_knownProperties = new List<string>();

			if (PropertyChanged != null)
				foreach (string s in old)
					PropertyChanged( this, new PropertyChangedEventArgs( s ) );
		}

		public event PropertyChangedEventHandler PropertyChanged;
	}
}
